1 module hip.hipaudio.backend.opensles.player; 2 3 4 version(Android): 5 import opensles.sles; 6 import opensles.android; 7 8 import hip.hipaudio.backend.opensles.source; 9 import hip.hipaudio.backend.opensles.clip; 10 import hip.hipaudio.audiosource; 11 import hip.hipaudio.backend.sles; 12 import hip.config.opts : HIP_OPENSLES_OPTIMAL, HIP_OPENSLES_FAST_MIXER; 13 import hip.audio_decoding.audio; 14 import hip.util.conv:to; 15 import hip.error.handler; 16 import hip.hipaudio.audio; 17 18 19 version(OpenSLES1) 20 alias SLDataFormat = SLDataFormat_PCM; 21 else 22 alias SLDataFormat = SLAndroidDataFormat_PCM_EX; 23 24 version(Android): 25 26 private SLDataFormat getFormatAsOpenSLES(AudioConfig cfg) 27 { 28 SLDataFormat ret; 29 version(OpenSLES1) 30 ret.formatType = SL_DATAFORMAT_PCM; 31 else 32 ret.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; 33 34 ret.numChannels = cfg.channels; //2 channels seems to not be supported yet 35 36 static if(HIP_OPENSLES_OPTIMAL) 37 ret.samplesPerSec = HipOpenSLESAudioPlayer.optimalSampleRate*1000; 38 else 39 ret.samplesPerSec = cfg.sampleRate*1000; 40 41 switch(cfg.format) 42 { 43 //Big 44 case AudioFormat.signed16Big: 45 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; 46 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; 47 ret.endianness = SL_BYTEORDER_BIGENDIAN; 48 version(OpenSLES1_1) 49 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; 50 break; 51 case AudioFormat.signed32Big: 52 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; 53 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; 54 ret.endianness = SL_BYTEORDER_BIGENDIAN; 55 version(OpenSLES1_1) 56 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; 57 break; 58 case AudioFormat.signed8: 59 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_8; 60 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; 61 ret.endianness = SL_BYTEORDER_LITTLEENDIAN; 62 version(OpenSLES1_1) 63 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; 64 break; 65 default: 66 case AudioFormat.signed16Little: 67 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; 68 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; 69 ret.endianness = SL_BYTEORDER_LITTLEENDIAN; 70 version(OpenSLES1_1) 71 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; 72 break; 73 case AudioFormat.signed32Little: 74 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; 75 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; 76 ret.endianness = SL_BYTEORDER_LITTLEENDIAN; 77 version(OpenSLES1_1) 78 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; 79 break; 80 //Little 81 case AudioFormat.float32Little: 82 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; 83 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; 84 ret.endianness = SL_BYTEORDER_LITTLEENDIAN; 85 version(OpenSLES1_1) 86 ret.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; 87 else 88 ErrorHandler.assertExit(false, "Needs OpenSLES 1.1 (Achieved by -version=OpenSLES1_1) to support float PCM"); 89 break; 90 case AudioFormat.float32Big: 91 ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; 92 ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; 93 ret.endianness = SL_BYTEORDER_BIGENDIAN; 94 version(OpenSLES1_1) 95 ret.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; 96 else 97 ErrorHandler.assertExit(false, "Needs OpenSLES 1.1 (Achieved by -version=OpenSLES1_1) to support float PCM"); 98 break; 99 } 100 101 if(cfg.channels == 2) 102 ret.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; 103 else if(cfg.channels == 1) 104 ret.channelMask = SL_SPEAKER_FRONT_CENTER; 105 else 106 ErrorHandler.assertExit(false, "OpenSL ES Audio Config does not supports " ~ 107 to!string(cfg.channels)~" channels"); 108 109 return ret; 110 } 111 112 package __gshared SLIAudioPlayer*[] playerPool; 113 package uint poolRingIndex = 0; 114 115 /// Probably should take into account fast mixer: https://source.android.com/devices/audio/latency/design 116 enum MAX_SLI_AUDIO_PLAYERS = 32; 117 enum MAX_SLI_BUFFERS = 16; 118 119 120 /** 121 * If wish to use fast mixer, this function should only create 122 * players with the same stats from the android output 123 */ 124 package SLIAudioPlayer* hipGenAudioPlayer() 125 { 126 SLDataFormat fmt = HipOpenSLESAudioPlayer.config.getFormatAsOpenSLES(); 127 version(Android) 128 { 129 import opensles.android; 130 SLDataLocator_AndroidSimpleBufferQueue locator; 131 locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; 132 ///This options says how many buffers it is possible to enqueue 133 locator.numBuffers = MAX_SLI_BUFFERS; 134 } 135 else 136 { 137 SLDataLocator_Address locator; 138 locator.locatorType = SL_DATALOCATOR_ADDRESS; 139 // locator.pAddress = bufferPtr; 140 // locator.length = bufferLength; 141 } 142 SLDataSource src; 143 src.pLocator = &locator; 144 src.pFormat = &fmt; 145 146 //Okay 147 SLDataLocator_OutputMix locatorMix; 148 locatorMix.locatorType = SL_DATALOCATOR_OUTPUTMIX; 149 locatorMix.outputMix = outputMix.outputMixObj; 150 151 SLDataSink destination; 152 destination.pLocator = &locatorMix; 153 destination.pFormat = null; 154 155 SLIAudioPlayer* player = sliGenAudioPlayer(src, destination); 156 if(player == null) 157 ErrorHandler.showErrorMessage("SLIAudioPlayer creation error:", sliGetErrorMessages()); 158 159 return player; 160 } 161 162 package SLIAudioPlayer* hipGetPlayerFromPool() 163 { 164 foreach(p; playerPool) 165 { 166 if(!p.isPlaying) 167 return p; 168 } 169 if(playerPool.length == MAX_SLI_AUDIO_PLAYERS) 170 { 171 SLIAudioPlayer* temp = playerPool[poolRingIndex]; 172 SLIAudioPlayer.stop(*temp); 173 poolRingIndex = (poolRingIndex+1)%MAX_SLI_AUDIO_PLAYERS; 174 return temp; 175 } 176 return hipGenAudioPlayer(); 177 } 178 179 class HipOpenSLESAudioPlayer : IHipAudioPlayer 180 { 181 SLIOutputMix output; 182 SLEngineItf itf; 183 184 __gshared 185 { 186 AudioConfig config; 187 bool hasProAudio; 188 bool hasLowLatencyAudio; 189 int optimalBufferSize; 190 int optimalSampleRate; 191 } 192 193 protected SLIAudioPlayer*[] spawnedPlayers; 194 195 /** 196 * For understanding a bit better how the buffer size works, take a look into the documentation 197 * provided by google: https://developer.android.com/ndk/guides/audio/audio-latency#buffer-size 198 * 199 * As the buffersize can be a multiple, for not sending a buffer too small, it is get the closest 200 * multiple to the optimal buffer size. 201 * 202 * If first play takes a greater latency, it will be time to implement warmup silence: 203 * https://developer.android.com/ndk/guides/audio/audio-latency#warmup-latency 204 */ 205 this 206 ( 207 AudioConfig cfg, 208 bool hasProAudio, 209 bool hasLowLatencyAudio, 210 int optimalBufferSize, 211 int optimalSampleRate 212 ) 213 { 214 import hip.math.utils:getClosestMultiple; 215 import hip.console.log; 216 optimalBufferSize = getClosestMultiple(optimalBufferSize, AudioConfig.defaultBufferSize); 217 HipOpenSLESAudioPlayer.hasProAudio = hasProAudio; 218 HipOpenSLESAudioPlayer.hasLowLatencyAudio = hasLowLatencyAudio; 219 HipOpenSLESAudioPlayer.optimalBufferSize = optimalBufferSize; 220 HipOpenSLESAudioPlayer.optimalSampleRate = optimalSampleRate; 221 static if(HIP_OPENSLES_OPTIMAL) 222 cfg.sampleRate = optimalSampleRate; 223 config = cfg; 224 225 226 logln("OpenSL ES Initialization with: 227 hasProAudio? ", hasProAudio, " 228 hasLowLatencyAudio? ", hasLowLatencyAudio, " 229 optimalBufferSize: ", optimalBufferSize, " 230 outputSampleRate: ", cfg.sampleRate, 231 cfg); 232 233 234 ErrorHandler.assertLazyErrorMessage(sliCreateOutputContext( 235 hasProAudio, 236 hasLowLatencyAudio, 237 optimalBufferSize, 238 config.sampleRate, 239 HIP_OPENSLES_FAST_MIXER 240 ), 241 "Error creating OpenSLES context.", sliGetErrorMessages()); 242 } 243 //LOAD RELATED 244 public bool play_streamed(AHipAudioSource src) 245 { 246 HipOpenSLESAudioSource source = cast(HipOpenSLESAudioSource)src; 247 static if(HIP_OPENSLES_OPTIMAL) 248 uint clipSize = cast(uint)HipOpenSLESAudioPlayer.optimalBufferSize; 249 else 250 uint clipSize = cast(uint)src.clip.getClipSize(); 251 252 SLIAudioPlayer.play(*source.audioPlayer); 253 return true; 254 } 255 public HipAudioClipAPI getClip(){return new HipOpenSLESAudioClip(new HipAudioDecoder(), HipAudioClipHint(config.channels, config.sampleRate, false, true));} 256 257 public HipAudioClipAPI loadStreamed(string path, uint chunkSize) 258 { 259 HipAudioClipAPI buffer = new HipOpenSLESAudioClip(new HipAudioDecoder(), HipAudioClipHint(config.channels, config.sampleRate, false, true), chunkSize); 260 // buffer.loadStreamed(path, getEncodingFromName(path)); 261 return buffer; 262 } 263 void updateStream(AHipAudioSource source){} 264 public void updateStreamed(AHipAudioSource source){} 265 266 public AHipAudioSource getSource(bool isStreamed) 267 { 268 SLIAudioPlayer* p = hipGetPlayerFromPool(); 269 spawnedPlayers~= p; 270 return new HipOpenSLESAudioSource(p, isStreamed); 271 } 272 273 public void onDestroy(){sliDestroyContext();} 274 275 public void update() 276 { 277 foreach(p; spawnedPlayers) 278 p.update(); 279 } 280 }